package com.jxs.easysecurity.http.spring;

import com.jxs.easysecurity.http.HttpSecurityService;
import com.jxs.easysecurity.http.SecurityValidationListener;
import com.jxs.easysecurity.resource.http.UriResource;
import com.jxs.easysecurity.resource.http.UriResourceDataSource;
import com.jxs.easysecurity.session.Session;
import com.jxs.easysecurity.session.SessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 权限验证拦截器，用于SpringMVC。
 * 你需要提供{@link SessionManager}和{@link UriResourceDataSource}两个Bean的自动注入。
 *
 * 默认的，没有标记为保护资源的URL都是匿名可访问的，如果URL能够从{@link UriResourceDataSource}匹配
 * 到一个资源，证明这个URL是保护资源，那么需要验证用户是否持有该资源ID。
 *
 * 你可以将一个控制器的方法标记为"非保护资源"，使用{@link Anonymous}注解，可添加到方法或类上，且该注解
 * 级别最高。
 *
 * 或者你可以标记为{@link LoginUser}，用户只要登录即可访问，不需要持有该资源的ID。
 *
 * 你可以继承本类，然后覆盖{@link #onRequest(HttpServletRequest, HttpServletResponse, Object, Session)}
 * 和{@link #onResourceFound(HttpServletRequest, HttpServletResponse, Object, Session, UriResource)}
 * 来在执行验证前执行你的逻辑。
 *
 * @see LoginUser
 * @see Anonymous
 *
 * @author jiangxingshang
 * @since 17/6/26
 */
public class SecurityValidationInterceptor implements HandlerInterceptor {

    private SessionManager sessionManager;
    @Autowired
    private UriResourceDataSource dataSource;
    private HttpSecurityService securityService;
    private boolean enabled = true;

    @Autowired
    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
        this.securityService = new HttpSecurityService(sessionManager);
    }

    //安全验证失败监听器
    private SecurityValidationListener securityValidationListener;
    public void setOnSecurityValidationFail(SecurityValidationListener securityValidationListener) {
        this.securityValidationListener = securityValidationListener;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {

        if(!enabled) return true;

        Session session = sessionManager.getSession();
        UriResource res = dataSource.match(req.getRequestURI(), req.getMethod());

        if(!onRequest(req, resp, handler, session)) {
            return false;
        }

        //找到匹配的资源后，交给拦截器
        if(res != null && !onResourceFound(req, resp, handler, session, res)) {
            return false;
        }

        if(handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod)handler;

            //Anonymous 非保护资源
            Anonymous methodAnonymous = handlerMethod.getMethod().getAnnotation(Anonymous.class);
            Anonymous classAnonymous = handlerMethod.getBeanType().getAnnotation(Anonymous.class);
            if(methodAnonymous != null || classAnonymous != null) {
                return true;
            }

            //OnlyLogin 只需登录即可访问的保护资源
            LoginUser loginUser = handlerMethod.getMethod().getAnnotation(LoginUser.class);
            if (loginUser == null) {
                loginUser = handlerMethod.getBeanType().getAnnotation(LoginUser.class);
            }
            if(loginUser != null) {
                if(!session.isLogin()) {
                    return validationFail(req, resp, HttpSecurityService.ValidationResult.NO_LOGIN);
                }
                Class<?>[] clzz = loginUser.filter();
                if (clzz.length > 0) {
                    for (Class<?> cls : clzz) {
                        LoginUserFilter filter = (LoginUserFilter) cls.newInstance();
                        if(filter.accept(req, resp, handlerMethod, session)) {
                            return true;
                        }
                    }
                    return validationFail(req, resp, HttpSecurityService.ValidationResult.NO_PRIVILEGE);
                } else {
                    return true;
                }
            }
        }

        //安全验证
        HttpSecurityService.ValidationResult result = securityService.validate(session, res);

        //处理验证结果
        if(result == HttpSecurityService.ValidationResult.IGNORE || result == HttpSecurityService.ValidationResult.SUCCESS) {
            return true;
        } else {
            return validationFail(req, resp, result);
        }
    }

    private boolean validationFail(HttpServletRequest req, HttpServletResponse resp, HttpSecurityService.ValidationResult result) {
        if(securityValidationListener != null) {
            securityValidationListener.onFail(req, resp, result);
        } else {
            resp.setStatus(401);
        }
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }

    /**
     * 接收到请求后立即执行（在验证权限之前），返回false可以阻止代码继续往下执行，但是你要自己处理Response消息。
     * @param request
     * @param response
     * @param handler
     * @param session
     * @return
     */
    public boolean onRequest(HttpServletRequest request, HttpServletResponse response, Object handler, Session session) {
        return true;
    }

    /**
     *
     * 当匹配到保护资源时，在{@link #onRequest(HttpServletRequest, HttpServletResponse, Object, Session)}之后，在权限验证之前执行。
     * 返回false可以阻止代码继续往下执行，但是你要自己处理Response消息。
     * @param request
     * @param response
     * @param handler
     * @param session
     * @param resource
     * @return
     */
    public boolean onResourceFound(HttpServletRequest request, HttpServletResponse response, Object handler, Session session, UriResource resource) {
        return true;
    }
}
